package solver

import (
	"fmt"
	"log"
	"slices"
)

type Discard struct{}

func (Discard) Write(p []byte) (n int, err error) {
	return
}

func init() {
	log.SetFlags(log.Ltime | log.Lmicroseconds)
	log.SetPrefix("Strategy: ")
	log.SetOutput(Discard{})
}

func merge(r [][]int, x int) [][]int {
	if len(r) == 0 {
		return [][]int{{x}}
	}
	for i, xs := range r {
		r[i] = append([]int{x}, xs...)
	}
	return r
}

func combine(xs []int, n int) [][]int {
	if n < 1 {
		return [][]int{}
	}
	if n == 1 {
		r := [][]int{}
		for _, x := range xs {
			r = append(r, []int{x})
		}
		return r
	}
	length := len(xs)
	if length < n {
		return [][]int{}
	}
	if length == n {
		return [][]int{xs}
	}
	return append(merge(combine(xs[1:], n-1), xs[0]), combine(xs[1:], n)...)
}

func calcBoxAndCellNum(rowNum int, colNum int) (boxNum int, cellNum int) {
	boxNum = rowNum/3*3 + colNum/3
	cellNum = rowNum%3*3 + colNum%3
	return
}

func calcRowAndColNum(boxNum int, cellNum int) (rowNum int, colNum int) {
	rowNum = boxNum/3*3 + cellNum/3
	colNum = boxNum%3*3 + cellNum%3
	return
}

func (c *Cell) String() string {
	return fmt.Sprintf("(r%vc%v %v)", c.rowNum, c.colNum, c.value)
}

func (c *Cell) solved() bool {
	return c.value != 0
}

func (c *Cell) setValue(value int) {
	c.value = value
	if value == 0 {
		c.candidateCount = 9
		c.candidates = [9]bool{true, true, true, true, true, true, true, true, true}
	} else {
		c.candidateCount = 1
		c.candidates = [9]bool{}
		c.candidates[value-1] = true
	}
}

func (c *Cell) removeCandidateBut(butCands []int) (used bool) {
	if c.solved() {
		return false
	}
	for i := 0; i < 9; i++ {
		if slices.Contains(butCands, i) || !c.candidates[i] {
			continue
		}
		c.candidates[i] = false
		c.candidateCount--
		used = true
	}
	return
}

func (c *Cell) removeCandidate(value int) bool {
	if c.candidateCount == 1 || !c.candidates[value-1] {
		return false
	}
	c.candidateCount -= 1
	c.candidates[value-1] = false
	return true
}

const (
	RowHouse int = iota
	ColHouse
	BoxHouse
)

func HouseType(x, y *Cell) int {
	if x.rowNum == y.rowNum {
		return RowHouse
	} else if x.colNum == y.colNum {
		return ColHouse
	} else if x.boxNum == y.boxNum {
		return BoxHouse
	} else {
		return 0
	}
}

func (h *House) HouseType() int {
	return HouseType(h.cells[0], h.cells[4])
}

func sumupCandCells(house *House) (candCells [9][9]bool, candCounts [9]int) {
	for cellNum, cell := range house.cells {
		if cell.solved() {
			continue
		}
		for cand, candidate := range cell.candidates {
			if candidate {
				candCells[cand][cellNum] = true
				candCounts[cand]++
			}
		}
	}
	return
}

func cellNumsForNaked(house *House, nakedCount int) []int {
	cellNums := []int{}
	for cellNum, cell := range house.cells {
		if cell.solved() || cell.candidateCount > nakedCount {
			continue
		}
		cellNums = append(cellNums, cellNum)
	}
	return cellNums
}

func clearupNakedHouse(house *House, cellNums []int, values []int) (used bool) {
	for i, cell := range house.cells {
		if cell.solved() || slices.Contains(cellNums, i) {
			continue
		}
		for _, value := range values {
			if cell.removeCandidate(value) {
				used = true
			}
		}
	}
	return
}

func (g *Grid) doStrategy(doStrategyHouse func(*House) bool) (used bool) {
	for _, box := range g.boxes {
		if doStrategyHouse(&box) {
			used = true
		}
	}

	for _, row := range g.rows {
		if doStrategyHouse(&row) {
			used = true
		}
	}

	for _, col := range g.cols {
		if doStrategyHouse(&col) {
			used = true
		}
	}
	return
}

func intersect(as []int, bs []int) []int {
	cs := []int{}
	for _, a := range as {
		if slices.Contains(bs, a) {
			cs = append(cs, a)
		}
	}
	return cs
}

func union(as []int, bs []int) []int {
	cs := []int{}
	cs = append(cs, as...)
	cs = append(cs, bs...)
	slices.Sort(cs)
	cs = slices.Compact(cs)
	return cs
}

func difference(as []int, bs []int) []int {
	cs := []int{}
	for _, a := range as {
		if !slices.Contains(bs, a) {
			cs = append(cs, a)
		}
	}
	return cs
}

func symDifference(as []int, bs []int) []int {
	return difference(union(as, bs), intersect(as, bs))
}
